﻿using System.Text;
using System.Web;
using Nexia.FluentHtmlElements.Utilities;

namespace Nexia.FluentHtmlElements.Elements
{
	/// <summary>
	/// The base class for all elements in the FluentHtmlElements library with could possibly contain an body.
	/// </summary>
	/// <typeparam name="T">The type of the return value of chaining methods.</typeparam>
	public class ElementWithBody<T> : ElementBase<T> where T : ElementWithBody<T>
	{
		private const int NoElipse = 0;

		protected ElementWithBody(string name)
			: this(name, new PlainTextConversion())
		{
		}

		protected ElementWithBody(string name, PlainTextConversion plainTextConversion)
			: base(name)
		{
			PlainTextConversion = plainTextConversion;
		}

		private readonly PlainTextConversion PlainTextConversion;
		private StringBuilder _innerBody;

		/// <summary>
		/// How the tag should be closed.
		/// </summary>
		protected internal override TagRenderMode TagRenderMode
		{
			get { return TagRenderMode.Normal; }
		}

		/// <summary>
		/// Sets the inner html of the tag.
		/// </summary>
		/// <param name="html"></param>
		/// <returns></returns>
		public virtual T InnerHtml(object html)
		{			
			ClearBody();
			AppendToInnerHtml(html);
			return (T)this;
		}

		/// <summary>
		/// Appends the string representation of a specified object to the end of the inner body of this tag.
		/// </summary>
		/// <param name="html">The object to append</param>
		/// <returns>this object</returns>
		public T AppendToInnerHtml(object html)
		{
			if (html != null)
				InnerBody.Append(html.ToString());
			return (T)this;
		}

		/// <summary>
		/// Sets the inner body of the tag to the the string representation of a specified object (encoding it first)
		/// </summary>
		/// <param name="text">The object to set</param>
		/// <returns>this object</returns>
		public virtual T InnerText(object text)
		{
			ClearBody();
			AppendToInnerHtml(text);
			return (T)this;
		}

		/// <summary>
		/// Appends the string representation of a specified object to the end of the inner body of this tag (encoding it first).
		/// </summary>
		/// <param name="text">The object to append</param>
		/// <returns>this object</returns>
		public T AppendToInnerText(object text)
		{
			if (text != null)
				InnerBody.Append(HttpUtility.HtmlEncode(text.ToString()));
			return (T)this;
		}

		/// <summary>
		/// Sets the number of characters to show in the body.  An elipse character will be inserted at the end.
		/// </summary>
		/// <remarks>The algorithm used to trim the text may not trim at exactly the number of characters given. The algorithm may be choose to trim on a work boundary.</remarks>
		/// <param name="length">The length to trim the text</param>
		/// <returns>this object</returns>
		public virtual T ElipseBody(int length)
		{
			ElipseLength = length;

			return (T) this;
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		/// <filterpriority>2</filterpriority>
		public override string ToString()
		{
			Builder.InnerHtml = GetInnerHtml();
			return base.ToString();
		}

		/// <summary>
		/// Does this tag have a none empty inner body.
		/// </summary>
		/// <returns></returns>
		protected virtual bool HasInnerBody()
		{
			return InnerBody.Length > 0;				
		}

		/// <summary>
		/// Appproximate elipse length for the body of the tag.
		/// </summary>
		protected virtual int ElipseLength
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or Sets the text stored as the body of the tag.
		/// </summary>
		/// <remarks>Stored as a StringBuilder to allow for AppendToBody later</remarks>
		protected virtual StringBuilder InnerBody
		{
			get
			{
				if (_innerBody == null)
				{
					_innerBody = new StringBuilder();
				}

				return _innerBody;
			}
			set
			{
				_innerBody = value;
			}
		}

		/// <summary>
		/// Get the inner html of the tag
		/// </summary>
		/// <returns></returns>
		protected virtual string GetInnerHtml()
		{
			return GetInnerBody(false);
		}

		/// <summary>
		/// Get the inner text of the tag. If the tag contains html, it will be stripped.
		/// </summary>
		/// <returns></returns>
		protected virtual string GetInnerText()
		{
			return GetInnerBody(true);
		}

		protected virtual string GetInnerBody(bool stripHtml)
		{
			var innerBody = InnerBody.ToString();
			if (ElipseLength != NoElipse)
				return PlainTextConversion.ConvertToPlainTextAndTrim(innerBody, ElipseLength);

			return stripHtml ? HttpUtility.HtmlEncode(innerBody) : innerBody;
		}	
	
		/// <summary>
		/// Clear all the text from the body.
		/// </summary>
		protected virtual void ClearBody()
		{
			InnerBody = new StringBuilder();
		}
	}
}
